import os, random, sys, traceback, time

from multiprocessing import Process, JoinableQueue, Event
from datetime import datetime, timedelta

import pylast

import lib, sql

from xdh_logging import initLog, LogWriter
from settings import settings
from cache import cache
from nothing import Nothing
from track import Track
from podcast_track import PodcastTrack
from MPD import MPD

log = initLog("lastfm")

API_KEY = "338670a42148bb5fc5f6624594cd5d0d"
API_SECRET = "6fe3ea0409a8c0e2506b554e336bfcf7"

def connect_LastFM():
	params = {
		"api_key": API_KEY,
		"api_secret": API_SECRET,
		"username": settings.lastfm.username,
		"password_hash": pylast.md5(settings.lastfm.password),
	}
	while True:
		try:
			return pylast.LastFMNetwork(**params)
		except pylast.MalformedResponseError, e:
			time.sleep(settings.data.sleep)
			

class NowPlayingWorker(Process):
	Q = JoinableQueue()
	started = Event()

	last_net = connect_LastFM()

	@classmethod
	def startup(cls):
		if not cls.is_running():
			cls.started.set()
			cls.workers = []
			for i in xrange(1):
				worker = Nothing()
				setattr(worker, "running", Event())
				setattr(worker, "process", cls(worker.running))
				cls.workers.append(worker)


	@classmethod
	def is_running(cls):
		if not hasattr(cls, "workers"):
			return False

		running = not (cls.Q.empty() and cls.started.is_set())

		for worker in cls.workers:
			if worker.process.is_alive():
				running |= worker.running.is_set()

		return running

	@classmethod
	def shutdown(cls):
		if not hasattr(cls, "workers"):
			return

		for worker in cls.workers:
			if worker.process.is_alive():
				log.debug(u"Sending stop to {name} Queue.".format(name = cls.__name__))
				cls.Q.put(lib.STOP)

		cls.Q.join()

		for worker in cls.workers:
			if worker.process.is_alive():
				worker.process.join()

		del cls.workers

		sql.Q.join()

		cls.started.clear()


	def __init__(self, running):
		super(self.__class__, self).__init__()

		self.running = running


		self.start()

	def initialize(self, filename):
		self.filename = filename
		if sql.PodcastTrackData.exists(filename):
			self.track = PodcastTrack(filename)
		else:
			self.track = Track(filename)
		duration = int(lib.sciRound(self.track.length))

		self.params = {}
		self.params["artist"] = self.track.artist
		self.params["title"] = self.track.title
		self.params["duration"] = duration

		album = self.track.album
		if album != None:
			self.params["album"] = album

		album_artist = self.track.album_artist
		if album_artist != None and album_artist != self.params["artist"]:
			self.params["album_artist"] = album_artist

		track_number = self.track.track_number
		if track_number != None:
			self.params["track_number"] = track_number

	def process(self):
		if self.params["artist"] == None or len(self.params["artist"].strip()) == 0 or self.params["title"] == None or len(self.params["title"].strip()) == 0 or self.params["duration"] < 30:
			return
		try:
			self.__class__.last_net.update_now_playing(**self.params)

		except pylast.WSError, e:
			if str(e.status) in ("8", "11", "16"):
				log.warning("Now Updating trapped Last.FM Error ({status}): {details}\n{network}".format(status = e.status, details = e.details, network = e.network))
				if e.status not in ("16"):
					time.sleep(settings.data.sleep)
					self.__class__.Q.put(self.filename)
			else:
				print "Last.FM Error ({status}): {details}\n{network}".format(status = e.status, details = e.details, network = e.network)
				raise

	def run(self):
		try:
			while True:
				item = self.__class__.Q.get()
				if item == lib.STOP:
					self.__class__.Q.task_done()
					log.debug("Got stop from {name} Queue.".format(name = self.__class__.__name__))
					break

				self.running.set()

				if not self.__class__.started.is_set():
					self.__class__.started.set()

				self.initialize(item)

				self.process()

				self.__class__.Q.task_done()

				self.running.clear()
		except Exception, e:
			log.error("{name}: {msg}".format(name = e.__class__.__name__, msg = e))
			log.error("".join(traceback.format_exception(*sys.exc_info())))


class ScrobbleWorker(Process):
	Q = JoinableQueue()
	started = Event()

	last_net = connect_LastFM()

	@classmethod
	def startup(cls):
		if not cls.is_running():
			cls.started.set()
			cls.workers = []
			for i in xrange(1):
				worker = Nothing()
				setattr(worker, "running", Event())
				setattr(worker, "process", cls(worker.running))
				cls.workers.append(worker)


	@classmethod
	def is_running(cls):
		if not hasattr(cls, "workers"):
			return False

		running = not (cls.Q.empty() and cls.started.is_set())

		for worker in cls.workers:
			if worker.process.is_alive():
				running |= worker.running.is_set()

		return running

	@classmethod
	def shutdown(cls):
		if not hasattr(cls, "workers"):
			return

		for worker in cls.workers:
			if worker.process.is_alive():
				log.debug(u"Sending stop to {name} Queue.".format(name = cls.__name__))
				cls.Q.put(lib.STOP)

		cls.Q.join()

		for worker in cls.workers:
			if worker.process.is_alive():
				worker.process.join()

		del cls.workers

		sql.Q.join()

		cls.started.clear()


	def __init__(self, running):
		super(self.__class__, self).__init__()

		self.running = running


		self.start()

	def initialize(self, batch):
		self.to_submit = []
		count = 0
		filenames_finished = []
		self.batch = []
		for filename, scrobble_date in batch:
			if sql.PodcastTrackData.exists(filename):
				track = PodcastTrack(filename)
			else:
				track = Track(filename)

			duration = int(lib.sciRound(track.length))
			if duration <= 30:
				filenames_finished.append(filename)
				continue

			artist = track.artist
			if artist == None or len(artist.strip()) == 0:
				filenames_finished.append(filename)
				continue

			title = track.title
			if title == None or len(title.strip()) == 0:
				filenames_finished.append(filename)
				continue

			count += 1
			if count > 50:
				reprocess = [(filename, scrobble_date) for filename, scrobble_date in batch if filename not in filenames_finished]
				self.__class__.Q.put(reprocess)
				break

			self.batch.append((filename, scrobble_date))
			filenames_finished.append(filename)

			params = {
				"artist": artist,
				"title": title,
				"timestamp": int(time.mktime((scrobble_date - timedelta(seconds = min(4 * 60, int(lib.sciRound(duration / 2.0))))).timetuple())),
				"duration": duration,
			}

			album = track.album
			if album != None:
				params["album"] = album
                
			album_artist = track.album_artist
			if album_artist != None and album_artist != params["artist"]:
				params["album_artist"] = album_artist
                
			track_number = track.track_number
			if track_number != None:
				params["track_number"] = track_number

			self.to_submit.append(params)

	def process(self):
		if len(self.to_submit) == 0:
			return

		try:
			self.__class__.last_net.scrobble_many(self.to_submit)

		except pylast.WSError, e:
			if str(e.status) in ("8", "11", "16"):
				log.warning("Scrobble trapped Last.FM Error ({status}): {details}\n{network}".format(status = e.status, details = e.details, network = e.network))
				time.sleep(settings.data.sleep)
				self.__class__.Q.put(self.batch)
			else:
				print "Last.FM Error ({status}): {details}\n{network}".format(status = e.status, details = e.details, network = e.network)
				raise

	def run(self):
		try:
			while True:
				item = self.__class__.Q.get()
				if item == lib.STOP:
					self.__class__.Q.task_done()
					log.debug("Got stop from {name} Queue.".format(name = self.__class__.__name__))
					break

				self.running.set()

				if not self.__class__.started.is_set():
					self.__class__.started.set()

				self.initialize(item)

				self.process()

				self.__class__.Q.task_done()

				self.running.clear()
		except Exception, e:
			log.error("{name}: {msg}".format(name = e.__class__.__name__, msg = e))
			log.error("".join(traceback.format_exception(*sys.exc_info())))


class LastFM(object):
	last_net = connect_LastFM()

	def __init__(self):
		if "is_scrobbled" not in cache:
			cache["is_scrobbled"] = False

	def __enter__(self):
		NowPlayingWorker.startup()
		ScrobbleWorker.startup()
		return self

	def __exit__(self, type, value, tback):
		ScrobbleWorker.shutdown()
		NowPlayingWorker.shutdown()

		ScrobbleWorker.Q.join()
		NowPlayingWorker.Q.join()

		if type != None:
			log.error("{name}: {msg}".format(name = value.__class__.__name__, msg = value))
			log.error("".join(traceback.format_exception(*sys.exc_info())))

	def now_playing(self, track):
		NowPlayingWorker.Q.put(track)
		cache["is_scrobbled"] = False

	def check(self):
		if not cache["is_scrobbled"] and "time" in MPD.status and ("last_scrobbled" not in cache or cache["last_scrobbled"] != lib.safe_unicode(MPD.current)):
			played, total = (int(num) for num in MPD.status["time"])
			if lib.lastFMScrobbleTime(played, total):
				current_filename = lib.safe_unicode(MPD.current)
				start_date = datetime.today() - timedelta(seconds = played)

				ScrobbleWorker.Q.put([(current_filename, start_date)])
				cache["is_scrobbled"] = True
				cache["last_scrobbled"] = current_filename

	@classmethod
	def scrobble_batch(cls, batch):
		ScrobbleWorker.Q.put(batch)

	@property
	def running(self):
		return ScrobbleWorker.is_running()

	@classmethod
	def process(cls, feeds = None):
		with LogWriter():
			with sql.RowWriter():
				with cls() as p:
					p.start(feeds)
					while p.running:
						time.sleep(settings.data.sleep)
					p.stop()

